using System;
using System.Text;
using System.Linq;
using Xunit;
using static SmartSite.Frame.FrameTypeEnum;

namespace SmartSite.Frame.Test
{
    public class TestIdentityAckFrame
    {
        [Fact]
        public void TestBase()
        {
            //Given
            IdentityAckFrame frame = new IdentityAckFrame();


            //When

            //Then
            /* 基本帧结构 */
            Assert.Equal(0, (int)frame.ProtocolVersion.Index);
            Assert.Equal(4, (int)frame.ProtocolVersion.Count);
            Assert.Equal(4, (int)frame.FactoryCode.Index);
            Assert.Equal(4, (int)frame.FactoryCode.Count);
            Assert.Equal(8, (int)frame.FrameType.Index);
            Assert.Equal(5, (int)frame.FrameType.Count);
            Assert.Equal(13, (int)frame.InfoLength.Index);
            Assert.Equal(11, (int)frame.InfoLength.Count);
            Assert.Equal(3 << 3, (int)frame.MonitorDeviceId.Index);
            Assert.Equal(16 << 3, (int)frame.MonitorDeviceId.Count);
            Assert.Equal(19 << 3, (int)frame.ValidationCode.Index);
            Assert.Equal(1 << 3, (int)frame.ValidationCode.Count);
            Assert.Equal(20 << 3, (int)frame.CommunicationId.Index);
            Assert.Equal(3 << 3, (int)frame.CommunicationId.Count);
            Assert.Equal(23 << 3, (int)frame.ServerDateTime.Index);
            Assert.Equal(4 << 3, (int)frame.ServerDateTime.Count);
            Assert.Equal(27 << 3, (int)frame.CRC.Index);
            Assert.Equal(2 << 3, (int)frame.CRC.Count);
            Assert.Equal(29, frame.Buffer.Count);

            Assert.Equal(4, frame.ProtocolVersion.Value);
            Assert.Equal(8, frame.InfoLength.Value);
            Assert.Equal(IdentityAckFrameType, frame.FrameType.Value);
        }


        [Theory]
        [InlineData("0000000000000223", new byte[] { 1, 2, 3 }, ValidationCodeEnum.DeviceDisabled)]
        [InlineData("1100000000000223", new byte[] { 4, 5, 6 }, ValidationCodeEnum.ErrorPwd)]
        [InlineData("2200000000000223", new byte[] { 7, 8, 9 }, ValidationCodeEnum.NoRegisterDeviceCode)]
        public void TestGeneral(string hardwareSerialNum, byte[] deviceCommunicateID, ValidationCodeEnum validationCode)
        {
            DateTime dateTime = DateTime.Now;

            IdentityAckFrame frame = new IdentityAckFrame();
            frame.FactoryCode.Value = 4;
            frame.MonitorDeviceId.Value = hardwareSerialNum;
            frame.ValidationCode.Value = validationCode;
            frame.CommunicationId.Value = deviceCommunicateID.ToULong();
            frame.ServerDateTime.Value = dateTime;
            frame.CRC.Value = frame.CalculationCRC();

            byte[] oridinBuffer = Oridin.GetDownFrameData(
                validationCode,
                hardwareSerialNum.ToBytes(), 8,
                deviceCommunicateID,
                IdentityAckFrameType,
                dateTime, false);

            Assert.Equal(oridinBuffer.Length, frame.Buffer.Count);

            for (int i = 0; i < frame.Buffer.Count; i++)
                Assert.Equal($"{i}: {oridinBuffer[i]}", $"{i}: {frame.Buffer[i]}");

            Assert.Equal(oridinBuffer, frame.Buffer);

            oridinBuffer = Oridin.GetDownFrameData(
                validationCode,
                hardwareSerialNum.ToBytes(), 8,
                deviceCommunicateID,
                IdentityAckFrameType,
                dateTime, true);
            Assert.Equal(oridinBuffer, frame.Buffer.ToList().PackFrameForSynchronization().ToArray());
        }
    }

    public static class Convert
    {
        public static byte[] ToBytes(this string str)
        {
            Encoding DefaultEncoding = Encoding.ASCII;
            byte[] Bytes = new byte[16];
            int count = DefaultEncoding.GetBytes(str, 0, str.Length, Bytes, 0);
            if (count < Bytes.Length) Array.Clear(Bytes, count, Bytes.Length - count);
            return Bytes;
        }

        public static ulong ToULong(this byte[] array)
        {
            ulong result = 0;
            foreach (byte item in array)
            {
                result <<= 8;
                result += item;
            }
            return result;
        }

    }


    public class Oridin
    {
        #region 身份验证回应帧
        /// <summary>
        /// 身份验证回应帧
        /// </summary>
        /// <param name="validationCode">验证代码</param>
        /// <param name="devicecode">设备代码</param>
        /// <param name="length">长度=8</param>
        /// <param name="deviceCommunicateID">3字节的通信ID</param>
        /// <returns></returns>
        public static byte[] GetDownFrameData(ValidationCodeEnum validationCode, byte[] devicecode, UInt16 length, byte[] deviceCommunicateID, FrameTypeEnum frametype, DateTime dateTime, bool is_uart_pkt)
        {
            int k = 0;
            var udpsendbuf = getHeaderInfo(devicecode, length, out k, frametype, false);

            #region 信息段
            udpsendbuf[k] = (byte)validationCode;//返回验证结果 ，1字节

            #region 3Byte的通信ID
            udpsendbuf[k + 1] = deviceCommunicateID[0];
            udpsendbuf[k + 2] = deviceCommunicateID[1];
            udpsendbuf[k + 3] = deviceCommunicateID[2];
            #endregion

            // DateTime collecttime = DateTime.Now;
            DateTime collecttime = dateTime;
            byte timetemp = (byte)collecttime.Month;
            timetemp = (byte)(timetemp >> 2);//年 月. 当前: 月份值的高两位
            udpsendbuf[k + 4] = (byte)(collecttime.Year - 2010);
            udpsendbuf[k + 4] = (byte)(udpsendbuf[23] << 2);
            udpsendbuf[k + 4] += timetemp; // 保存月份值的高两位, 到buf[23]的第两位中

            timetemp = (byte)collecttime.Month;
            timetemp = (byte)(timetemp << 6);//月 天 时. 当前: 月份值高两位溢出, 月份值低两位在临时变量的高两位
            udpsendbuf[k + 5] = (byte)collecttime.Day;
            udpsendbuf[k + 5] = (byte)(udpsendbuf[24] << 1); // 将Day值, 存储在buf[24]的5..1位置
            udpsendbuf[k + 5] += timetemp;  // 存储月份值高两位在buf[24]的7..6位置
            timetemp = (byte)collecttime.Hour;
            timetemp = (byte)(timetemp >> 4); // 保留Hour的最高1位
            udpsendbuf[k + 5] += timetemp; // Hour的最高1位, 存储在buf[24]的0位

            timetemp = (byte)collecttime.Hour;
            timetemp = (byte)(timetemp << 4);//时 分. 当前: Hour低4位, 存储在临时变量的高4位
            udpsendbuf[k + 6] = (byte)collecttime.Minute;
            udpsendbuf[k + 6] = (byte)(udpsendbuf[25] >> 2); // 存储Minute在buf[25]的低4位
            udpsendbuf[k + 6] += timetemp; // 存储Hour低4位在buf[25]的高4位

            timetemp = (byte)collecttime.Minute;
            timetemp = (byte)(timetemp << 6);//分 秒. 当前: 存储Minute低2位在临时变量高2位
            udpsendbuf[k + 7] = (byte)collecttime.Minute;  // ! 此处应该是Second
            udpsendbuf[k + 7] = (byte)collecttime.Second;  // ! 改正为Second
            udpsendbuf[k + 7] += timetemp; // 存储Minute低2位在buf[27]的7..6位
            #endregion

            var totallength = getDownFrameCalculate(ref udpsendbuf, (UInt16)(k + length), is_uart_pkt);

            byte[] result = new byte[totallength];
            for (int i = 0; i < totallength; i++)
                result[i] = udpsendbuf[i];
            return result;
        }
        #endregion

        #region 获取头信息
        /// <summary>
        /// 获取头信息
        /// </summary>
        /// <param name="devicecode">通信编码</param>
        /// <param name="infolength">信息段长度</param>
        /// <param name="headerlength">信息段之前长度</param>
        /// <param name="flag">设备编号类型:true:通信编号;false:原始(16B)的编号</param>
        /// <returns></returns>
        private static byte[] getHeaderInfo(byte[] devicecode, UInt16 infolength, out int headerlength, FrameTypeEnum frameType, bool flag = true)
        {
            byte[] udpsendbuf = new byte[1024];
            #region 固定协议

            //* 完成 */
            udpsendbuf[0] = 0x44;//高4bit为协议版本，固定为0x04；低4bit为厂家代码，0-3保留，4～15用于区分不同厂家

            /* 完成 */
            UInt16 stemp = (byte)frameType;
            stemp = (UInt16)(stemp << 11);//把帧类型的值移到高位去（移到帧类型本位上），这样的话，就防止其他数据写入而不会破坏帧类型数据。

            /* 完成 */
            stemp += infolength;  //
            udpsendbuf[2] = (byte)stemp;//信息长度占42byte，占低11bit
            stemp = (UInt16)(stemp >> 8);//获取高8位的数据，组成一个字节发送，解析的是要右移3位，才能取出帧类型数据
            udpsendbuf[1] = (byte)stemp;//帧类型 ，占5bit 高字节


            /**
            * flag = false 
            * * 依然使用硬件序列号
            * 完成
            */
            if (!flag)
            {
                for (int i = 0; i < 16; i++)//固定16个字节的设备代码
                {
                    udpsendbuf[3 + i] = devicecode[i];
                }
            }
            else
            {

                for (int i = 0; i < 3; i++)
                {
                    udpsendbuf[3 + i] = devicecode[i];
                }
            }
            #endregion
            headerlength = flag ? 6 : 19;
            return udpsendbuf;
        }
        #endregion


        #region 获取下行帧校验和
        /// <summary>
        /// 获取下行帧校验和
        /// </summary>
        /// <param name="udpsendbuf"></param>
        /// <param name="length"></param>
        private static UInt16 getDownFrameCalculate(ref byte[] udpsendbuf, UInt16 length, bool is_uart_pkt)
        {
            #region 校验码
            UInt16 temp = crc16_calculate(udpsendbuf, length);
            udpsendbuf[length + 1] = (byte)temp;// 校验和（2byte),udpsendbuf[84] 取低8位，
            temp = (UInt16)(temp >> 8);//取校验和的高8位
            udpsendbuf[length] = (byte)temp;

            UInt16 stemp = (UInt16)(length + 2);
            if (is_uart_pkt)
                hex_to_uart_pkt(ref udpsendbuf, ref stemp);
            #endregion
            return stemp;
        }
        #endregion

        #region 为下行帧添加头部和尾部
        /// <summary>
        /// 为下行帧添加头部和尾部
        /// </summary>
        /// <param name="hex_buf">数据</param>
        /// <param name="hex_buf_len">长度</param>
        /// <returns></returns>
        public static bool hex_to_uart_pkt(ref byte[] hex_buf, ref UInt16 hex_buf_len)
        {
            byte[] hex_buf_temp = new byte[hex_buf_len + 20];

            UInt16 index = 0;
            UInt16 old_hex_buf_len = hex_buf_len;

            if (old_hex_buf_len == 0)
            {
                return false;
            }

            hex_buf_temp[index++] = FrameDownType.DLE;
            hex_buf_temp[index++] = FrameDownType.STX;

            for (UInt16 i = 0; i < old_hex_buf_len; i++)
            {
                if (hex_buf[i] == FrameDownType.DLE)
                {
                    hex_buf_temp[index++] = FrameDownType.DLE;
                    hex_buf_temp[index++] = hex_buf[i];
                }
                else
                {
                    hex_buf_temp[index++] = hex_buf[i];
                }
            }

            hex_buf_temp[index++] = FrameDownType.DLE;
            hex_buf_temp[index++] = FrameDownType.ETX;

            hex_buf_temp.CopyTo(hex_buf, 0);
            hex_buf_len = index;

            return true;
        }
        #endregion


        #region 校验和
        /// <summary>
        /// 校验和
        /// </summary>
        /// <param name="pucSendBuf"></param>
        /// <param name="usLen"></param>
        /// <returns></returns>
        public static UInt16 crc16_calculate(byte[] pucSendBuf, UInt16 usLen)
        {
            UInt16 usCrc = 0xFFFF;

            for (UInt16 i = 0; i < usLen; i++)
            {
                usCrc ^= (UInt16)pucSendBuf[i];
                for (UInt16 j = 0; j < 8; j++)
                {
                    if ((usCrc & 1) != 0)
                    {
                        usCrc >>= 1;
                        usCrc ^= 0xA001;
                    }
                    else
                    {
                        usCrc >>= 1;
                    }
                }
            }

            return usCrc;
        }
        #endregion


        #region 下行帧格式
        /// <summary>
        /// DLE STX 帧头 DLE ETX 帧尾 数据中有DLE,使用DLE DLE替代
        /// </summary>
        public struct FrameDownType
        {
            /// <summary>
            ///数据信息
            /// </summary>
            public const byte DLE = 0xfe;    // DLE STX 帧头 DLE ETX 帧尾 数据中有DLE,使用DLE DLE替代
                                             /// <summary>
                                             /// 开始
                                             /// </summary>
            public const byte STX = 0xfb;
            /// <summary>
            /// 结束
            /// </summary>
            public const byte ETX = 0xfa;
        }
        #endregion

    }
}